一開始寫 react 後就知道 redux 狀態管理的方式,所以也很快就引入到專案使用,直到最近就突然想到,好像沒有很好的去理解他的演變XD
這邊就觀念就簡單帶過去,主要是在簡易的實現實作跟比較差異,其中也有連結很多很棒的文章,想看更多可以參考文章連結~
redux
一開始主要是去處理跨多層頁面的傳值,所以選擇 redux 來做狀態管理。
可以參考 超Q神人 的文章
React 與他的快樂小夥伴 Redux -事件處理(Handling events)
底下就來用 新增貓貓的功能
來當範例吧XD
$ npm i redux react-redux
//action.js
export const ADD_CAT = "ADD_CAT";
export const getMoreCat = (name) => {
return {
type: ADD_CAT,
payload: {
cat: name,
},
};
};
//reducer.js
import { ADD_CAT } from "./action";
const initState = { list: ["橘貓"] };
const catReducer = (state = initState, action) => {
switch (action.type) {
case ADD_CAT:
console.log("cat payload", action.payload);
return { ...state, list: [...state.list, action.payload.cat] };
default:
return state;
}
};
export default catReducer;
要記得中央 store 要把剛剛貓貓的 reducer 引入
//store.js
import { createStore } from "redux";
import catReducer from "./reducer";
let store = createStore(catReducer);
export default store;
App.js 也不要忘了帶入剛剛設定的 store
//app.js
import { Provider } from "react-redux";
import store from "./redux/store";
import Cat from "./Cat"; //發起 dispatch 的貓貓元件
function App() {
return (
<Provider store={store}>
<div className="App">
<Cat />
</div>
</Provider>
);
}
export default App;
最後在一般的 component connect 我們的 redux
//Cat.js
import React from "react";
import { connect } from "react-redux";
import { getMoreCat } from "./redux/action";
const Cat = ({ list, getMoreCat, getMoreCatApi }) => {
console.log("cat list", list);
return (
<div>
<botton onClick={() => getMoreCat("虎斑貓")}>增加貓貓</botton>
</div>
);
};
const mapStateToProps = (state) => ({ list: state.list });
const mapDispatchToProps = (dispatch) => {
return {
getMoreCat: (name) => dispatch(getMoreCat(name)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Cat);
好拉,現在如果我們要從遠端 request 貓貓 api 怎麼辦
這邊用 the cat api : GET https://api.thecatapi.com/v1/categories
btw 剛剛 redux 是用 array
並新增一個 axios 的 request
$npm i axios
//request.js
import axios from "axios";
export const fetchGetCats = async () => {
try {
const { data } = await axios.get("https://api.thecatapi.com/v1/categories");
return data;
} catch (error) {
console.log("error", error);
}
};
在 action 新增 getMoreCatApi
然後把剛剛的 api request 放進去
//這是錯誤使用方式
//acion.js
export const getMoreCatApi = async () => {
try {
const response = await fetchGetCats();
return {
type: ADD_CAT,
payload: {
cat: response,
},
};
} catch (error) {
console.log("error");
}
const response = "api貓貓";
};
別忘了新增一個 增加遠端貓貓 的 button 連接剛剛我們定義的 getMoreCatApi
先在 mapDispatchToProps connect 我們定義的 getMoreCatApi
getMoreCatApi: () => dispatch(getMoreCatApi()),
然後新增 "增加遠端貓貓的" button
<button onClick={() => getMoreCatApi()}>增加遠端貓貓</button>
貓貓元件如下
//Cat.js
import React from "react";
import { connect } from "react-redux";
import { getMoreCat, getMoreCatApi } from "./redux/action";
const Cat = ({ list, getMoreCat, getMoreCatApi }) => {
console.log("list", list);
return (
<div>
<button onClick={() => getMoreCat("虎斑貓")}>增加貓貓</button>
<br />
<button onClick={() => getMoreCatApi()}>增加遠端貓貓</button>
</div>
);
};
const mapStateToProps = (state) => ({ list: state.list });
const mapDispatchToProps = (dispatch) => {
return {
getMoreCat: (name) => dispatch(getMoreCat(name)),
getMoreCatApi: () => dispatch(getMoreCatApi()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Cat);
登愣,會出現異步的問題 err
搭拉,這時候 thunk 就派上用場了拉
thunk
在使用 thunk 前,可以先看看 詳解 Redux Middleware ,了解一下 middleware 在 redux 是扮演什麼角色
我覺得他這圖片解釋得非常好,middleware 在 action 被指派後,在 reducer 之前,可以去做額外處理
那就開始使用 redux-thunk 囉~
$npm i redux-thunk
而要變動的地方也不多,先在 store 引入 thunk 的 middleware
記得要在 createStore 裡把 thunk 加入到 redux 的 middleware 裡
//store.js
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import catReducer from "./reducer";
const store = createStore(catReducer, applyMiddleware(thunk));
export default store;
action 則藉由 dispatch callback 去呼叫剛剛定義過的 getMoreCat
return async (dispatch) => {
...
dispatch(getMoreCat(response));
...
};
因此 action 會是長這樣
//action.js
export const getMoreCatApi = () => {
console.log("get more cats with api");
return async (dispatch) => {
try {
const response = await fetchGetCats();
dispatch(getMoreCat(response));
} catch (error) {
console.log("error");
}
};
};
這邊應該可以看到有成功藉由 thunk 的去處理 api 的異步處理
saga
其實 thunk 就可以解決 redux 異步問題,但又出現 redux-saga 出現,為了解決持續增長導致難以維護的 action
開始使用 saga 囉~
$npm i redux-saga
先處理好 store 與 saga 的 middleware
import createSagaMiddleware from "redux-saga";
import { createStore, applyMiddleware } from "redux";
import catReducer from "./reducer";
import rootSaga from "./saga";
const sagaMiddleware = createSagaMiddleware();
const store = createStore(catReducer, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(rootSaga);
export default store;
saga 這邊適用 generator function 使用 yield 一步一步下去,監聽 FETCH_DATA 的動作,然後定義等 api response 回來後再用原本定義的 ADD_CAT 去跟 reducer 溝通新增貓貓
//saga.js
import { call, put, takeEvery } from "redux-saga/effects";
import { ADD_CAT } from "./action";
import { fetchGetCats } from "../api/request";
export const FETCH_DATA = "FETCH_DATA";
function* fetchData() {
const data = yield call(() => fetchGetCats());
yield put({ type: ADD_CAT, payload: data });
}
function* mySaga() {
yield takeEvery(FETCH_DATA, fetchData);
}
export default mySaga;
可以看到 Cat 這邊是直接 dispatch 到 saga 去做 FETCH_DATA 處理
//Cat.js
import React from "react";
import { connect } from "react-redux";
import { getMoreCat } from "./redux/action";
import { FETCH_DATA } from "./redux/saga";
const Cat = ({ list, getMoreCat, getMoreCatApi }) => {
console.log("list", list);
return (
<div>
<button onClick={() => getMoreCat("虎斑貓")}>增加貓貓</button>
<br />
<button onClick={() => getMoreCatApi()}>增加遠端貓貓</button>
</div>
);
};
const mapStateToProps = (state) => ({ list: state.list });
const mapDispatchToProps = (dispatch) => {
return {
getMoreCat: (name) => dispatch(getMoreCat(name)),
getMoreCatApi: () => {
dispatch({ type: FETCH_DATA });
},
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Cat);
ok 了拉,可以看到藉由 saga 的去處理 api 的異步處理
這篇 saga 寫法是參考超Q神人的 Redux Saga | Redux 界的非同步救星 - 基本用法,所以想要更了解觀念也可以看看這篇
redux-thunk 與 redux-saga 的差異
redux-thunk | redux-saga |
---|---|
使用上較為簡單 | 程式複雜度較高 |
持續增長 Action 容易過於龐大難以維護 | 切分較為乾淨,較容易維護 |
較適合入門者學習 | 建議先了解 thunk 後再切換 |
可以說 redux thunk 的 fetch api 事件處理是這樣流程
頁面點擊 新增遠端貓貓 按鈕 →
action 裡運用 thunk 去做異步處理 →
reducer 去 parse 資料
而 redux saga 的 fetch api 事件處理是這樣流程
頁面點擊 新增遠端貓貓 按鈕 →
在 saga 去做異步處理 →
reducer 去 parse 資料
可以看到 saga 很明顯把異步處理另外抽出來做處理了,所以應該會比較易讀跟好維護吧 (燦笑)
兩年前寫一篇 thunk 跟 saga 的比較也只寫到一半 😂,如今終於 run 完整個流程了。不過沒有寫過 saga 的 test ,感覺未來也可以試試,感覺挺有趣的~